package com.xjh.serverinspect.config;

import com.xjh.serverinspect.security.PasswordUtils;
import com.xjh.serverinspect.util.CommandConf;
import com.xjh.serverinspect.util.XmlUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

/**
 * <p>
 * bean注册配置
 * </p>
 *
 * @author xujinghui
 * @since 2024-10-15
 */
@Configuration
@Slf4j
public class BeanRegistryConfig {

    /**
     * 外部配置文件路径
     */
    @Value("${inspect.config.location:}")
    private String confPath;

    /**
     * 获取命令配置
     *
     * @return CommandConf
     */
    @Bean
    public CommandConf getCommandConf() {
        InputStream inputStream = null;
        try {
            encryptPassword();

            // 优先从外部配置文件路径加载
            if (StringUtils.hasText(confPath)) {
                inputStream = Files.newInputStream(Paths.get(confPath));
            } else {
                inputStream = new ClassPathResource("command-conf.xml").getInputStream();
            }

            JAXBContext jaxbContext = JAXBContext.newInstance(CommandConf.class);
            CommandConf commandConf = (CommandConf) jaxbContext.createUnmarshaller().unmarshal(inputStream);

            List<CommandConf.Server> servers = commandConf.getServers();
            for (CommandConf.Server server : servers) {
                sqlPasswordEncrypt(server.getSql());
                shellPasswordEncrypt(server.getShell());
            }

            sqlPasswordEncrypt(commandConf.getSql());
            shellPasswordEncrypt(commandConf.getShell());

            return commandConf;
        } catch (IOException e) {
            throw new RuntimeException("Failed to load command-conf.xml", e);
        } catch (JAXBException e) {
            throw new RuntimeException("Failed to parse command-conf.xml", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("Failed to close command-conf.xml input stream", e);
                }
            }
        }
    }

    private boolean sqlPasswordEncrypt(CommandConf.Sql sql) {
        if (sql != null) {
            String sqlPassword = sql.getPassword();
            if (StringUtils.hasText(sqlPassword) && !PasswordUtils.isEncrypted(sqlPassword)) {
                sql.setPassword(PasswordUtils.encrypt(sqlPassword));
                return true;
            }
        }
        return false;
    }

    private boolean shellPasswordEncrypt(CommandConf.Shell shell) {
        if (shell != null) {
            String sshPassword = shell.getPassword();
            if (StringUtils.hasText(sshPassword) && !PasswordUtils.isEncrypted(sshPassword)) {
                shell.setPassword(PasswordUtils.encrypt(sshPassword));
                return true;
            }
        }
        return false;
    }

    private void encryptPassword() {
        if (StringUtils.hasText(confPath)) {
            File outFile = new File(confPath);
            try (InputStream inputStream = Files.newInputStream(Paths.get(confPath))) {
                Document document = XmlUtils.parseXmlFile(inputStream);
                NodeList nodeList = document.getElementsByTagName("password");
                for (int i = 0; i < nodeList.getLength(); i++) {
                    Node node = nodeList.item(i);
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        Element element = (Element) node;
                        String value = element.getTextContent();

                        if (StringUtils.hasText(value) && !PasswordUtils.isEncrypted(value)) {
                            element.setTextContent(PasswordUtils.encrypt(value));
                        }
                    }
                }

                XmlUtils.saveXmlFile(document, outFile);
            } catch (IOException | ParserConfigurationException | SAXException e) {
                throw new RuntimeException("Failed to parse command-conf.xml", e);
            } catch (TransformerException e) {
                throw new RuntimeException("Failed to encrypt password in command-conf.xml", e);
            }
        }
    }

}